﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using System.Collections;
using SourceRestore.IconHandler;
using System.Diagnostics;
using System.Security.Principal;

namespace SourceRestore
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private bool autoCheckOn = false;
        private Hashtable hashTableIcons = new Hashtable();
        private int directoryCount = 0;
        private int fileCount = 0;

        #region Form events
        private void MainForm_Load(object sender, EventArgs e)
        {
            if (Properties.Settings.Default.MainFormSize != new Size(0, 0))
            {
                this.Location = Properties.Settings.Default.MainFormLocation;
                this.Size = Properties.Settings.Default.MainFormSize;
            }
            else
            {
                this.Location = new Point((Screen.PrimaryScreen.WorkingArea.Width - this.Width) / 2, (Screen.PrimaryScreen.WorkingArea.Height - this.Height) / 2);
            }
            txtZipFile.Text = Properties.Settings.Default.LastZipFile;
            txtRootDirectory.Text = Properties.Settings.Default.LastCompareDirectory;
            hashTableIcons.Add("Folder", null);
            hashTableIcons.Add("File", null);
            restartInAdminModeToolStripMenuItem.Visible = !IsAdmin();
            pictureBoxShield.Visible = IsAdmin();
            this.Text += " - " + Application.ProductVersion;
        }
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (WindowState == FormWindowState.Normal)
            {
                Properties.Settings.Default.MainFormLocation = this.Location;
                Properties.Settings.Default.MainFormSize = this.Size;
            }
            Properties.Settings.Default.LastZipFile = txtZipFile.Text;
            Properties.Settings.Default.LastCompareDirectory = txtRootDirectory.Text;
            Properties.Settings.Default.Save();
        }
        #endregion

        #region TreeView events
        private void tvwDirectories_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (autoCheckOn)
                return;
            if (e.Node.ImageIndex == 0)
            {
                autoCheckOn = true;
                SetAllChildNodeChecks(e.Node);
                autoCheckOn = false;
            }
            int checkedCount = tvwDirectories.CheckedCount();
            cmdCopyChecked.Enabled = checkedCount > 0;
            toolStripStatusLabelCheckedCount.Text = string.Format("{0} item(s) checked", checkedCount);
        }
        private void tvwDirectories_AfterSelect(object sender, TreeViewEventArgs e)
        {
            exploreToolStripMenuItem.Enabled = tvwDirectories.SelectedNode != null && txtRootDirectory.Text.Length > 0 && Directory.Exists(txtRootDirectory.Text);
        }
        #endregion

        #region Button events
        private void cmdSetZip_Click(object sender, EventArgs e)
        {
            dlg.FileName = txtZipFile.Text;
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtZipFile.Text = dlg.FileName;
            }
        }
        private void cndBrowseDir_Click(object sender, EventArgs e)
        {
            folderBrowserDialog1.SelectedPath = txtRootDirectory.Text;
            if (folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtRootDirectory.Text = folderBrowserDialog1.SelectedPath;
            }
        }
        private void cmdDoCompare_Click(object sender, EventArgs e)
        {

            toolStripStatusLabelProgress.Text = "Loading tree...";
            LoadTreeView(txtZipFile.Text, txtRootDirectory.Text);
        }
        private void cmdCopyChecked_Click(object sender, EventArgs e)
        {
            int bufferSize = 2048;
            long totalSize = 0;
            int copyCount = 0;
            ZipFile zf = new ZipFile(txtZipFile.Text);
            foreach (TreeNode node in tvwDirectories.CheckedNodes())
            {
                if (node.Tag != null && node.Tag.ToString() == "File")
                {
                    string zipFilePath = node.FullPath.Replace("Root\\", "");
                    string targetPath = System.IO.Path.Combine(txtRootDirectory.Text, zipFilePath);

                    toolStripStatusLabelProgress.Text = string.Format("Copying {0}", zipFilePath);
                    Application.DoEvents();
                    ZipEntry zipFile = (from ZipEntry z in zf
                                        where z.Name.TrimEnd('\0') == zipFilePath
                                        select z).FirstOrDefault();
                    if (zipFile != null)
                    {
                        try
                        {
                            Stream outStream = zf.GetInputStream(zipFile);
                            if (File.Exists(targetPath))
                            {
                                File.SetAttributes(targetPath, FileAttributes.Normal);
                                File.Delete(targetPath);
                            }
                            if (!Directory.Exists(Path.GetDirectoryName(targetPath)))
                            {
                                Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
                            }
                            using (FileStream fs = File.Create(targetPath))
                            {
                                byte[] data = new byte[bufferSize];
                                int readCount = outStream.Read(data, 0, bufferSize);
                                while (readCount > 0)
                                {
                                    fs.Write(data, 0, readCount);
                                    readCount = outStream.Read(data, 0, bufferSize);
                                }
                                fs.Flush();
                                fs.Close();
                                totalSize += zipFile.Size;
                                copyCount++;
                            }
                            FileInfo fi = new FileInfo(targetPath);
                            if (fi.Exists)
                            {
                                fi.LastWriteTime = zipFile.DateTime;
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(string.Format("Error extracting to {0}\r\n{1}", targetPath, ex.ToString()), "Extracting file", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        }
                    }
                }
            }
            toolStripStatusLabelProgress.Text = "Done";
            zf.Close();
            totalSize = totalSize / 1024;
            MessageBox.Show(string.Format("{0} file(s) copied\r\nTotal size: {1}KB\r\n", copyCount, totalSize), "Files restored", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        #endregion

        #region Input events
        private void txtZipFile_TextChanged(object sender, EventArgs e)
        {
            timerCanCompare.Enabled = false;
            timerCanCompare.Enabled = true;
        }
        private void txtRootDirectory_TextChanged(object sender, EventArgs e)
        {
            timerCanCompare.Enabled = false;
            timerCanCompare.Enabled = true;
        }
        #endregion

        #region Timer events
        private void timerCanCompare_Tick(object sender, EventArgs e)
        {
            this.Invoke((MethodInvoker)delegate
            {
                CanCompareNow();
            });
        }
        #endregion

        #region Context events
        private void exploreToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string path = txtRootDirectory.Text;
            if (tvwDirectories.SelectedNode != null && tvwDirectories.SelectedNode.Tag != null)
            {

                if (tvwDirectories.SelectedNode.Tag.ToString() == "File")
                    path = Path.GetDirectoryName(Path.Combine(txtRootDirectory.Text, tvwDirectories.SelectedNode.FullPath.Replace("Root\\", "")));
                else
                    path = Path.Combine(txtRootDirectory.Text, tvwDirectories.SelectedNode.FullPath.Replace("Root\\", ""));
            }
            System.Diagnostics.Process.Start("explorer.exe", path);
        }
        #endregion

        #region Private methods
        private void LoadTreeView(string zipFile, string compareToRoot)
        {
            TreeNode root = tvwDirectories.Nodes[0];
            root.Nodes.Clear();
            toolStripStatusLabelCheckedCount.Text = string.Format("{0} item(s) checked", 0);
            directoryCount = 0;
            fileCount = 0;

            ZipFile zf = new ZipFile(txtZipFile.Text);
            //load directories
            foreach (string directoryPath in (from ZipEntry z in zf
                                              group z by System.IO.Path.GetDirectoryName(z.Name.TrimEnd('\0')) into g
                                              where g.Key.Length > 0
                                              orderby g.Key
                                              select g.Key))
            {
                List<string> tree = GetDirectoryTree(directoryPath);
                TreeNode rootTree = root;
                for (int i = 0; i < tree.Count; i++)
                {
                    if (rootTree.Nodes.ContainsKey(tree[i]))
                        rootTree = rootTree.Nodes[tree[i]];
                    else
                    {
                        rootTree.Nodes.Add(tree[i], tree[i]);
                        rootTree = rootTree.Nodes[tree[i]];
                        rootTree.ImageIndex = 0;
                        rootTree.SelectedImageIndex = 0;
                        rootTree.Tag = "Directory";
                        directoryCount++;
                    }
                }
            }
            root.Expand();
            LoadFiles(root, zf, compareToRoot);
            zf.Close();
            toolStripStatusLabelProgress.Text = string.Format("{0} directories, {1} file(s) found", directoryCount, fileCount);
        }
        private void LoadFiles(TreeNode root, ZipFile zf, string compareToRoot)
        {
            string directoryPath = "";
            if (root.FullPath.Length > 5)
                directoryPath = root.FullPath.Substring(5);
            foreach (ZipEntry fileInZip in (from ZipEntry z in zf
                                            where System.IO.Path.GetDirectoryName(z.Name.TrimEnd('\0')) == directoryPath
                                            select z))
            {
                string fileName = System.IO.Path.GetFileName(fileInZip.Name.TrimEnd('\0'));
                if (Properties.Settings.Default.IgnoreFileTypes.Contains(System.IO.Path.GetExtension(fileName)))
                    continue;

                DateTime fileDate = fileInZip.DateTime;
                TreeNode fileNode = root.Nodes.Add(fileName, fileName);
                fileNode.ImageIndex = 1;
                fileNode.SelectedImageIndex = 1;
                fileNode.Tag = "File";
                fileCount++;

                string compareTo = System.IO.Path.Combine(compareToRoot, directoryPath, fileName);
                FileInfo fiCompareTo = new FileInfo(compareTo);
                if (fiCompareTo.Exists)
                {
                    if (fileDate > fiCompareTo.LastWriteTime)
                        fileNode.Checked = true;
                }
                else
                    fileNode.Checked = true;
                if (fileNode.Checked && !root.IsExpanded)
                    fileNode.ExpandAllParents(); // root.Expand();

                string extension = System.IO.Path.GetExtension(fileName).ToUpper();
                if (!hashTableIcons.ContainsKey(extension))
                {
                    imageListTree.Images.Add(GetFileAssociatedIcon.GetFileIcon(fileName)); //Add to imageList
                    hashTableIcons.Add(extension, imageListTree.Images.Count - 1); //Record position
                }

                fileNode.ImageIndex = (int)hashTableIcons[extension];
                fileNode.SelectedImageIndex = (int)hashTableIcons[extension];
            }
            foreach (TreeNode subDirectoryNode in root.Nodes)
            {
                LoadFiles(subDirectoryNode, zf, compareToRoot);
            }
        }
        private List<string> GetDirectoryTree(string directoryPath)
        {
            List<string> tree = new List<string>();
            tree.Add(Path.GetFileName(directoryPath));
            string workPath = Path.GetDirectoryName(directoryPath);

            DirectoryInfo di = new DirectoryInfo(directoryPath);
            while (workPath.Length > 0)
            {
                tree.Insert(0, Path.GetFileName(workPath));
                workPath = Path.GetDirectoryName(workPath);
            }
            return tree;
        }
        private void CanCompareNow()
        {
            timerCanCompare.Enabled = false;
            cmdDoCompare.Enabled = txtZipFile.Text.Length > 0 && txtRootDirectory.Text.Length > 0 &&
                            File.Exists(txtZipFile.Text) && Directory.Exists(txtRootDirectory.Text);
        }
        private void SetAllChildNodeChecks(TreeNode parentNode)
        {
            foreach (TreeNode childNode in parentNode.Nodes)
            {
                childNode.Checked = parentNode.Checked;
                SetAllChildNodeChecks(childNode);
            }
        } 
        #endregion

        #region Menu events
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OptionsWindow optionsWindow = new OptionsWindow();
            optionsWindow.ShowDialog();
        } 
        #endregion

        private void restartInAdminModeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.Save();
            RestartInAdminMode(false);
        }

        #region Adminmode
        private void RestartInAdminMode(bool noprompt)
        {
            if (noprompt || MessageBox.Show("Restart application in Administrative mode?", "Admin mode", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == System.Windows.Forms.DialogResult.Yes)
            {
                Properties.Settings.Default.Save();
                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.UseShellExecute = true;
                startInfo.WorkingDirectory = Environment.CurrentDirectory;
                startInfo.FileName = Application.ExecutablePath;

                startInfo.Verb = "runas";
                try
                {
                    Process p = Process.Start(startInfo);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    return;
                }
                Application.Exit();
            }
        }
        bool IsAdmin()
        {
            string strIdentity;
            try
            {
                AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
                WindowsIdentity wi = WindowsIdentity.GetCurrent();
                WindowsPrincipal wp = new WindowsPrincipal(wi);
                strIdentity = wp.Identity.Name;

                if (wp.IsInRole(WindowsBuiltInRole.Administrator))
                    return true;
                else
                    return false;
            }
            catch
            {
                return false;
            }

        }
        #endregion
        
    }
}
